package cn.monkey.family.data.crud;

import cn.monkey.family.data.*;
import cn.monkey.family.data.context.RequestContext;
import cn.monkey.family.data.crud.repository.QueryRequestRepository;
import cn.monkey.family.data.utils.ClassUtil;
import org.mapstruct.factory.Mappers;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.util.Collection;
import java.util.List;

public abstract class AbstractCrudService<ID, Q extends QueryRequest, T extends Identifiable<ID>, D, V, MAPPER extends DataMapper<D, T, V>,
        REPOSITORY extends QueryRequestRepository<T, ID, Q>> implements CrudService<ID, Q, D, V> {

    protected final REPOSITORY repository;

    protected final MAPPER mapper;

    public AbstractCrudService(REPOSITORY repository, MAPPER mapper) {
        this.repository = repository;
        this.mapper = mapper;
    }

    public AbstractCrudService(REPOSITORY repository) {
        this.repository = repository;
        this.mapper = Mappers.getMapper(ClassUtil.getActualType(this, CrudService.class, "MAPPER"));
    }

    @Override
    public Result<V> readById(RequestContext requestContext, ID id) {
        return this.repository.findById(id)
                .map(this.mapper::toVo)
                .map(Results::ok)
                .orElse(Results.notFound());
    }

    @Override
    public Result<V> readOne(RequestContext requestContext, Q queryRequest) {
        return this.repository.findOneByQueryRequest(queryRequest)
                .map(this.mapper::toVo)
                .map(Results::ok)
                .orElse(Results.notFound());
    }

    @Override
    public Result<Collection<V>> readAll(RequestContext requestContext, Q queryRequest, Sort sort) {
        List<V> list = this.repository.findAllByQueryRequest(queryRequest, sort)
                .stream()
                .map(this.mapper::toVo)
                .toList();
        return Results.ok(list);
    }

    @Override
    public Result<Collection<V>> readAll(RequestContext requestContext, Q queryRequest) {
        return this.readAll(requestContext, queryRequest, Sort.unsorted());
    }

    @Override
    public Result<Page<V>> readPage(RequestContext requestContext, Q queryRequest, Pageable pageable) {
        Page<T> page = this.repository.findPageByQueryRequest(queryRequest, pageable);
        return Results.ok(page.map(this.mapper::toVo));
    }

    @Override
    public Result<V> create(RequestContext requestContext, D d) {
        T save = this.repository.save(this.mapper.toEntity(d));
        return Results.ok(this.mapper.toVo(save));
    }

    @Override
    public Result<V> update(RequestContext requestContext, ID id, D d) {
        return this.repository.findById(id)
                .map(old -> this.mapper.merge(old, this.mapper.toEntity(d)))
                .map(this.repository::save)
                .map(this.mapper::toVo)
                .map(Results::ok)
                .orElse(Results.notFound());
    }

    @Override
    public Result<Void> delete(RequestContext requestContext, ID id) {
        this.repository.deleteById(id);
        return Results.ok();
    }

    @Override
    public Result<Void> deleteAll(RequestContext requestContext, Collection<ID> ids) {
        this.repository.deleteAllById(ids);
        return Results.ok();
    }
}
